package com.guanglan.dht_vampire.routing;

import com.guanglan.dht_vampire.bucket.Kbucket;
import com.guanglan.dht_vampire.node.NodeId;
import com.guanglan.dht_vampire.node.NodeInfo;
import java.math.BigInteger;
import java.util.*;

/**
 * @author 光蓝Comet
 * @description 路由表
 * @date 2018/8/21
 */
public class RoutingTable {

    //桶集合
    public final List<Kbucket> buckets = new ArrayList<>();

    //自身节点初始化路由表
    public RoutingTable(NodeInfo nodeInfo){
        // 创建初始化的桶, 范围为0到2的160次方, 添加我们自身的节点到桶中
        Kbucket kbucket = new Kbucket(new BigInteger("0"), new BigInteger("2").pow(160));
        kbucket.addNode(nodeInfo);

        Kbucket[] kbuckets;

        while ((kbuckets = kbucket.fissionKbucket()) != null){
            buckets.remove(kbucket);
            buckets.add(kbuckets[0]);
            buckets.add(kbuckets[1]);
            kbucket = kbuckets[0];
        }

        kbucket.clear();
    }

    public List<NodeInfo> findNearNodes(NodeId nodeId){
        int x = 0;
        for (int i = 0 ; i < buckets.size() ; i++){
           if (buckets.contains(nodeId.getNodeId())){ x = i; break;}
        }
        //获取自身节点所在桶中的所有节点
        final Collection<NodeInfo> kbuckets = buckets.get(x).getNodes();
        final List<NodeInfo> nodes = new ArrayList<>(kbuckets);
        //节点左侧所有节点集合
        final List<NodeInfo> leftNodes = new ArrayList<>();
        //节点右侧所有节点集合
        final List<NodeInfo> rightNodes = new ArrayList<>();

        // 获取目标id所在的桶的左边的桶的节点直到获取够8个以上
        for(int i = x - 1; i >= 0; i--) {
            leftNodes.addAll(buckets.get(i).getNodes());
            if(leftNodes.size() >= 8) break;
        }

        // 获取目标id所在的桶的右边的桶的节点直到获取够8个以上
        for(int i = x + 1; i < buckets.size(); i++) {
            rightNodes.addAll(buckets.get(i).getNodes());
            if(rightNodes.size() >= 8) break;
        }

        nodes.addAll(leftNodes);
        nodes.addAll(rightNodes);
        // 所有的距离保存在TreeSet中, 利用TreeSet维护自然顺序的特性, 获取的第一个就是最近的距离, 最后一个就是最远的距离
        Set<BigInteger> distances = new TreeSet<>();
        // 距离对应的节点列表, 因为同样的距离可能有左右两个节点
        Map<BigInteger, List<NodeInfo>> distance2Node = new HashMap<>();
        for(NodeInfo n : nodes) {
            // 两个id的距离为他们id转化为无符号数后的差值的绝对值
            BigInteger distance = n.getNid().subtract(nodeId.getNodeId()).abs();
            distances.add(distance);

            // 设置距离对应的节点
            List<NodeInfo> value;
            if(null == (value = distance2Node.get(distance))) {
                value = new ArrayList<>();
                distance2Node.put(distance, value);
            }
            value.add(n);
        }

        // 将要返回的节点, 从TreeSet中依次取出最近距离的节点, 满8个后结束
        List<NodeInfo> result = new ArrayList<>();
        for(BigInteger distance : distances) {
            result.addAll(distance2Node.get(distance));
            if(result.size() >= 8) break;
        }

        // 只返回8个节点
        return result.size() > 8 ? result.subList(0, 8) : result;
    }

    /**
     * 添加节点到路由表
     * @param node 需要添加的节点
     * @return
     */
    public void addNode(NodeInfo node) {
        BigInteger id = node.getNid();
        for(Kbucket bucket : buckets) {
            // 如果id在当前桶范围内, 则添加到当前桶
            if(bucket.contains(id)) {
                bucket.addNode(node);
                break;
            }
        }
    }

}
